﻿using System;
using System.ServiceModel;
using PSE.ProcessManager.Common.Gateway;
using PSE.ProcessManager.Server.Process;
using Sinacor.Infra.Common.Exceptions.FaultContracts;
using Sinacor.Infra.Common.Validation;
using Sinacor.Infra.Service.ExceptionHandling;
using Sinacor.Infra.Core.ProcessManager.Server.Security;
using PSE.ProcessManager.Server.Process.Security;
using System.Collections.Generic;
using Sinacor.Infra.Service.Data;
using System.Threading;
using PSE.ProcessManager.Common.Process;
using Sinacor.Infra.Common.Exceptions;
using PSE.Framework.ErrorLogging;
using Sinacor.Infra.Common.Security.Authentication.Credentials;
using System.Diagnostics;

namespace Sinacor.Infra.Core.ProcessManager.Server
{
    public abstract class SinacorBaseProcessServer<T> : SinacorBaseProcessServer
        where T : BaseProcessLogic, new()
    {
        protected T ProcessLogic { get; private set; }

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public SinacorBaseProcessServer()
            : base()
        {
            this.CreateProcessLogic();
        }

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public SinacorBaseProcessServer(int id, string name)
            : base(id, name)
        {
            this.CreateProcessLogic();
        }

        private void CreateProcessLogic()
        {
            this.ProcessLogic = new T();

            this.ProcessLogic.StatusSent += new EventHandler<PSE.ProcessManager.Common.Process.ProcessEventArgs<PSE.ProcessManager.Common.Process.ProcessStatus>>(ProcessLogic_StatusSent);
            this.ProcessLogic.Aborted += new EventHandler(ProcessLogic_Aborted);
            this.ProcessLogic.Finalized += new EventHandler(ProcessLogic_Finalized);
            this.ProcessLogic.Failed += new EventHandler(ProcessLogic_Failed);
            this.ProcessLogic.ProcessThrowAnException += new EventHandler<PSE.ProcessManager.Common.Process.ProcessEventArgs<Exception>>(ProcessLogic_ProcessThrowAnException);

            this.Aborted += new EventHandler(SinacorBaseProcessServer_Aborted);
        }

        void ProcessLogic_StatusSent(object sender, PSE.ProcessManager.Common.Process.ProcessEventArgs<PSE.ProcessManager.Common.Process.ProcessStatus> e)
        {
            this.Status = e.Parameter;
            this.SendStatus();
        }

        void ProcessLogic_Aborted(object sender, EventArgs e)
        {
            if (!this.IsAborted)
                this.Abort();
        }

        void ProcessLogic_Finalized(object sender, EventArgs e)
        {
            this.Finalize();
        }

        void ProcessLogic_Failed(object sender, EventArgs e)
        {
            this.Finalize(true);
        }

        void ProcessLogic_ProcessThrowAnException(object sender, ProcessEventArgs<Exception> e)
        {
            this.HandleException(e.Parameter);
        }

        void SinacorBaseProcessServer_Aborted(object sender, EventArgs e)
        {
            if (!this.ProcessLogic.IsAborted)
                this.ProcessLogic.Abort();
        }
    }

    public abstract class SinacorBaseProcessServer : BaseProcess<Sinacor.Infra.Core.ProcessManager.Server.Configuration.ProcessConfigurationSection>
    {
        private int _companyId = -1;

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public SinacorBaseProcessServer()
            : base()
        {
            //setar a chave de criptografia dos dados de configuração
            this.ConfigurationCryptoKey = GetCryptoKey();
        }

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public SinacorBaseProcessServer(int id, string name)
            : base(id, name)
        {
            //setar a chave de criptografia dos dados de configuração
            this.ConfigurationCryptoKey = GetCryptoKey();
        }

        private byte[] GetCryptoKey()
        {
            byte[] keyCopy = new byte[Constants.CRYPTO_KEY.Length];
            Constants.CRYPTO_KEY.CopyTo(keyCopy, 0);
            return keyCopy;
        }


        /// <summary>
        /// Inicializa o modo transacional de acesso ao banco de dados
        /// </summary>
        /// <param name="companyId">Identificador da empresa</param>
        protected void BeginTransaction(int companyId)
        {
            _companyId = companyId;
            TransactionCollection.GetInstance().CreateConnectionTransaction(System.Threading.Thread.CurrentThread.ManagedThreadId, companyId);
        }

        /// <summary>
        /// "Entrega" as alterações, inclusões ou deleções feitas no banco de dados
        /// </summary>
        protected void CommitTransaction()
        {
            if (_companyId >= 0)
                TransactionCollection.GetInstance().CommitTransaction(System.Threading.Thread.CurrentThread.ManagedThreadId, _companyId);
        }

        /// <summary>
        /// Descarta as alterações, inclusões ou deleções feitas no banco de dados
        /// </summary>
        protected void RollbackTransaction()
        {
            if (_companyId >= 0)
                TransactionCollection.GetInstance().RollbackTransaction(System.Threading.Thread.CurrentThread.ManagedThreadId, _companyId);
        }

        /// <summary>
        /// TODO: não utilizar mais reflection, utilizar os types do Common.Exceptions
        /// Converte a fault gerada pelo serviço para uma SinacorException específica.
        /// </summary>
        /// <param name="ex">Exception Gerada na classe de comunicação</param>
        ///example: Tipos de Exception tratadas: BusinessException, ValidationException, GeneralException, CommunicationException e Exception
        protected override BaseMessage CreateExceptionMessage(Exception ex)
        {
            if (ex == null)
                throw new ArgumentNullException("ex");

            BaseMessage message = null;

            if (ex is FaultException<BusinessFault>)
            {
                ex = ((FaultException<BusinessFault>)ex).Detail.CreateException();
            }
            else if (ex is FaultException<ValidationFault>)
            {
                ex = ((FaultException<ValidationFault>)ex).Detail.CreateException();
            }
            else if (ex is FaultException<InvalidSessionFault>)
            {
                ex = ((FaultException<InvalidSessionFault>)ex).Detail.CreateException();
            }
            else if (ex is FaultException<LicensingFault>)
            {
                ex = ((FaultException<LicensingFault>)ex).Detail.CreateException();
            }
            else if (ex is FaultException<AccessIsDeniedFault>)
            {
                ex = ((FaultException<AccessIsDeniedFault>)ex).Detail.CreateException();
            }
            else if (ex is FaultException<GeneralFault>)
            {
                ex = ((FaultException<GeneralFault>)ex).Detail.CreateException();
            }

            Dictionary<string, object> extensionData = new Dictionary<string, object>();
            extensionData.Add("ProcessId", this.Id);
            extensionData.Add("ProcessName", this.Name);

            // Grava o erro no log
            ExceptionShieldingAttribute.LogException(Sinacor.Infra.Common.Configuration.SystemComponents.Process, ref ex, extensionData);

            // Gera a Mensagem a partir da Exception
            if (ex is BaseException)
            {
                message = new Message<BaseFault>();
                ((Message<BaseFault>)message).Parameter = ((BaseException)ex).CreateFault();
            }
            else
            {
                message = new Message<string>();
                ((Message<string>)message).Parameter = ex.Message;
            }

            return message;
        }

        protected override void ReceivedMessage(BaseMessage message, object process)
        {

            UpdateAuthenticationContextData();
            UpdateAuthorizationContext();

            base.ReceivedMessage(message, process);

            if (this.IsAborted)
                this.RollbackTransaction();
        }

        private void UpdateAuthenticationContextData()
        {
            try
            {
                if (AuthenticationContext<SinacorAuthenticationData>.Current == null)
                    AuthenticationContext<SinacorAuthenticationData>.Current = new AuthenticationContext<SinacorAuthenticationData>();

                int index = OperationContext.Current.IncomingMessageHeaders.FindHeader(ClaimsMessageHeader.Name, ClaimsMessageHeader.NameSpace);
                if (index >= 0)
                {
                    ClaimsMessageHeader athzHeader = OperationContext.Current.IncomingMessageHeaders.GetHeader<ClaimsMessageHeader>(index);
                    AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData = SinacorAuthenticationDataBuilder.CreateAuthenticationData(athzHeader.Data);
                }
                //caso nao receba o header com os dados de autenticacao, tentar criar utilizando os dados de configuracao
                else
                {
                    AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData = SinacorAuthenticationDataBuilder.CreateAuthenticationData(this.Configuration);
                }

                CredentialRepository rep = CredentialRepository.GetInstance();

                rep.UserInfo.CompanyId = AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.CompanyId;
                rep.UserInfo.Password = AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.Password;
                rep.UserInfo.UserName = AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.LoginName;
                rep.UserInfo.UserId= AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.UserId;
            }
            catch (Exception ex)
            {
                this.HandleException(new Exception("There is an error updating authentication context, see inner exception for more details", ex), true);
            }
        }

        private void UpdateAuthorizationContext()
        {
            try
            {
                if (AuthorizationContext<SinacorAuthorizationContext>.Current == null)
                    AuthorizationContext<SinacorAuthorizationContext>.Current = new SinacorAuthorizationContext();
            }
            catch (Exception ex)
            {
                this.HandleException(new Exception("There is an error updating authorization context, see inner exception for more details", ex), true);
            }
        }

        public override string GetUserName()
        {
            if (AuthenticationContext<SinacorAuthenticationData>.Current != null && AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData != null)
                return AuthenticationContext<SinacorAuthenticationData>.Current.AuthenticationData.LoginName;
            else
                return string.Empty;
        }

        protected override PSE.Framework.Configuration.SystemComponent GetSystem()
        {
            return Sinacor.Infra.Common.Configuration.SystemComponents.System;
        }

        protected override PSE.Framework.Configuration.SystemComponent GetComponent()
        {
            return Sinacor.Infra.Common.Configuration.SystemComponents.Process;
        }
    }
}
